# -*- coding: utf-8 -*-
# Scikit-learn-5.py
"""
### 实验题目
#### 题目5：支持向量机（SVM）与核方法在非线性分类任务中的应用
**任务描述**
使用支持向量机（SVM）及其不同核函数（线性、多项式、径向基函数RBF）对**月亮数据集（moons）** 和**环形数据集（circles）** 进行分类，探索核方法在处理非线性可分数据时的能力，并比较不同核函数的分类性能与决策边界。
**数据集**
使用 `sklearn.datasets` 中的：
- `make_moons`：生成月亮形分布的二分类数据  
- `make_circles`：生成环形分布的二分类数据  
**步骤**  
1. **数据生成与可视化**  
   - 生成月亮和环形数据集，并可视化其分布。
2. **使用不同核函数的SVM进行分类**  
   - 分别使用以下核函数训练SVM模型：
     - 线性核（linear）
     - 多项式核（poly）
     - 径向基核（RBF）
   - 对每个数据集进行训练并评估准确率。
3. **绘制决策边界**  
   - 对每个核函数绘制决策边界，直观理解其分类机制。
4. **超参数调优**  
   - 对RBF核进行超参数调优（如 `C` 和 `gamma`），使用网格搜索（GridSearchCV）寻找最优参数。
5. **性能对比与分析**  
   - 比较不同核函数在相同数据集上的准确率、训练时间和决策边界的复杂度。
   - 分析哪些核更适合哪种类型的数据分布。
**预期输出**  
- 月亮和环形数据集的原始分布图
- 三种核函数在两个数据集上的决策边界可视化图
- 超参数调优前后的性能对比表格
- 对核函数选择与数据分布之间关系的简要分析
"""

# 配置选项
USE_RANDOM_SEARCH = False  # 设置为True使用随机搜索替代网格搜索
N_JOBS = -1  # 使用的CPU核心数，-1表示使用所有核心
CV_VERBOSE = 3  # 是否打印搜索进度，0：不打印，1或2或3：打印

# 导入基类模块
from ScikitLearnBase import ScikitLearnExperiment, setup_chinese_font
# 导入数据集
from sklearn.datasets import make_moons, make_circles
# 导入SVM相关模块
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV, train_test_split
from sklearn.metrics import accuracy_score
# 导入其他必要库
import numpy as np
import matplotlib.pyplot as plt
import time
from scipy.stats import loguniform, randint
import warnings
warnings.filterwarnings('ignore')

# 确保字体设置正确
setup_chinese_font()

# 创建实验实例
experiment = ScikitLearnExperiment("支持向量机与核方法在非线性分类任务中的应用")

# 打印实验标题
experiment.print_header()

# 打印配置信息
print(f"配置信息:")
print(f"- 搜索方法: {'随机搜索' if USE_RANDOM_SEARCH else '网格搜索'}")
print(f"- 使用CPU核心数: {'所有' if N_JOBS == -1 else N_JOBS}")
print()

# 1. 数据生成与可视化
print("1. 生成月亮和环形数据集并可视化")

# 生成数据集
moons_X, moons_y = make_moons(n_samples=500, noise=0.2, random_state=42)
circles_X, circles_y = make_circles(n_samples=500, noise=0.1, factor=0.5, random_state=42)

# 可视化数据集分布
fig, axes = plt.subplots(1, 2, figsize=(15, 6))

# 月亮数据集可视化
axes[0].scatter(moons_X[:, 0], moons_X[:, 1], c=moons_y, cmap=plt.cm.coolwarm, 
                s=50, edgecolors='k', alpha=0.7)
axes[0].set_title('月亮数据集 (Moons)')
axes[0].set_xlabel('特征 1')
axes[0].set_ylabel('特征 2')

# 环形数据集可视化
axes[1].scatter(circles_X[:, 0], circles_X[:, 1], c=circles_y, cmap=plt.cm.coolwarm, 
                s=50, edgecolors='k', alpha=0.7)
axes[1].set_title('环形数据集 (Circles)')
axes[1].set_xlabel('特征 1')
axes[1].set_ylabel('特征 2')

plt.tight_layout()
experiment.save_plot("原始数据集分布.png")
plt.show()

# 2. 使用不同核函数的SVM进行分类
print("2. 使用不同核函数的SVM进行分类")

# 定义核函数列表
kernels = ['linear', 'poly', 'rbf']
datasets = {
    '月亮数据集': (moons_X, moons_y),
    '环形数据集': (circles_X, circles_y)
}

# 存储结果
results = {}
total_training_time = 0  # 总训练时间

# 用于绘制决策边界的函数
def plot_decision_boundary(X, y, model, ax, title):
    h = 0.02  # 网格步长
    x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
    y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    
    # 预测整个网格
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    # 绘制决策边界和训练点
    ax.contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.coolwarm)
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.coolwarm, 
               s=30, edgecolors='k', alpha=0.7)
    ax.set_title(title)
    ax.set_xlim(xx.min(), xx.max())
    ax.set_ylim(yy.min(), yy.max())
    ax.set_xlabel('特征 1')
    ax.set_ylabel('特征 2')

# 对每个数据集和每个核函数进行训练和评估
for dataset_name, (X, y) in datasets.items():
    print(f"\n处理 {dataset_name}:")
    
    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )
    
    # 标准化数据
    scaler = experiment.scaler
    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)
    
    dataset_results = {}
    
    # 对每个核函数进行训练和评估
    for kernel in kernels:
        print(f"  使用 {kernel} 核函数...")
        
        # 记录开始时间
        start_time = time.time()
        
        # 创建并训练SVM模型
        if kernel == 'poly':
            svm = SVC(kernel=kernel, degree=3, random_state=42)
        else:
            svm = SVC(kernel=kernel, random_state=42)
        
        svm.fit(X_train, y_train)
        
        # 记录训练时间
        training_time = time.time() - start_time
        total_training_time += training_time
        
        # 预测并计算准确率
        y_pred = svm.predict(X_test)
        accuracy = accuracy_score(y_test, y_pred)
        
        # 存储结果
        dataset_results[kernel] = {
            'model': svm,
            'accuracy': accuracy,
            'training_time': training_time
        }
        
        print(f"    准确率: {accuracy:.4f}, 训练时间: {training_time:.4f}秒")
    
    results[dataset_name] = dataset_results

# 3. 绘制决策边界
print("\n3. 绘制决策边界")

# 为每个数据集绘制决策边界
for dataset_name, (X, y) in datasets.items():
    fig, axes = plt.subplots(1, 3, figsize=(18, 5))
    fig.suptitle(f'{dataset_name} - 不同核函数的决策边界', fontsize=16)
    
    dataset_results = results[dataset_name]
    
    for i, kernel in enumerate(kernels):
        model = dataset_results[kernel]['model']
        accuracy = dataset_results[kernel]['accuracy']
        
        # 绘制决策边界
        plot_decision_boundary(X, y, model, axes[i], 
                              f'{kernel}核 (准确率: {accuracy:.4f})')
    
    plt.tight_layout()
    experiment.save_plot(f"{dataset_name}_决策边界.png")
    plt.show()

# 4. 超参数调优（对RBF核和多项式核）
print("\n4. 超参数调优")

# 定义参数网格
rbf_param_grid = {
    'C': [0.01, 0.1, 1, 10, 100, 1000],
    'gamma': [0.001, 0.01, 0.1, 1, 10, 100]
}

# 定义参数分布（用于随机搜索）
rbf_param_dist = {
    'C': loguniform(1e-2, 1e3),
    'gamma': loguniform(1e-3, 1e2)
}

poly_param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': [0.01, 0.1, 1, 10],
    'degree': [2, 3, 4]
}

poly_param_dist = {
    'C': loguniform(1e-1, 1e2),
    'gamma': loguniform(1e-2, 1e1),
    'degree': randint(2, 5)
}

# 多次运行搜索并取平均结果
def multiple_parameter_search(X, y, param_grid, param_dist, kernel='rbf', n_runs=5, cv=5):
    """多次运行参数搜索并取平均结果"""
    best_params_list = []
    best_scores = []
    total_search_time = 0
    
    for i in range(n_runs):
        # 每次使用不同的随机种子划分数据
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=0.3, random_state=i
        )
        
        # 标准化数据
        scaler = experiment.scaler
        X_train = scaler.fit_transform(X_train)
        X_test = scaler.transform(X_test)
        
        # 创建搜索对象
        if kernel == 'poly':
            estimator = SVC(kernel='poly', random_state=42)
        else:
            estimator = SVC(kernel='rbf', random_state=42)
            
        # 选择搜索方法
        if USE_RANDOM_SEARCH:
            search = RandomizedSearchCV(
                estimator,
                param_dist,
                n_iter=30,  # 随机搜索的迭代次数
                cv=cv,
                scoring='accuracy',
                n_jobs=N_JOBS,
                random_state=42,
                verbose=CV_VERBOSE  
            )
        else:
            search = GridSearchCV(
                estimator,
                param_grid,
                cv=cv,
                scoring='accuracy',
                n_jobs=N_JOBS,
                verbose=CV_VERBOSE
            )
        
        # 执行搜索并计时
        start_time = time.time()
        search.fit(X_train, y_train)
        search_time = time.time() - start_time
        total_search_time += search_time
        
        # 存储结果
        best_params_list.append(search.best_params_)
        best_scores.append(search.best_score_)
        
        # 在测试集上评估最佳模型
        best_model = search.best_estimator_
        y_pred = best_model.predict(X_test)
        test_accuracy = accuracy_score(y_test, y_pred)
        
        print(f"  运行 {i+1}/{n_runs}: 最佳参数 {search.best_params_}, "
              f"交叉验证准确率: {search.best_score_:.4f}, "
              f"测试集准确率: {test_accuracy:.4f}, "
              f"搜索时间: {search_time:.2f}秒")
    
    # 计算平均结果
    avg_score = np.mean(best_scores)
    avg_search_time = total_search_time / n_runs
    print(f"  平均交叉验证准确率: {avg_score:.4f}")
    print(f"  平均搜索时间: {avg_search_time:.2f}秒")
    
    # 找出最常见的参数组合
    from collections import Counter
    param_counts = Counter([str(params) for params in best_params_list])
    most_common_params = eval(param_counts.most_common(1)[0][0])
    
    print(f"  最常见参数组合: {most_common_params}")
    
    return most_common_params, avg_score, avg_search_time

# 对每个数据集进行调优
tuning_start_time = time.time()

for dataset_name, (X, y) in datasets.items():
    print(f"\n对 {dataset_name} 进行超参数调优:")
    
    # RBF核调优
    print(f"  RBF核调优:")
    rbf_best_params, rbf_avg_score, rbf_search_time = multiple_parameter_search(
        X, y, rbf_param_grid, rbf_param_dist, kernel='rbf', n_runs=5, cv=5
    )
    
    # 使用最常见参数训练最终模型
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )
    
    scaler = experiment.scaler
    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)
    
    rbf_model = SVC(kernel='rbf', **rbf_best_params, random_state=42)
    rbf_model.fit(X_train, y_train)
    rbf_accuracy = accuracy_score(y_test, rbf_model.predict(X_test))
    
    # 多项式核调优
    print(f"  多项式核调优:")
    poly_best_params, poly_avg_score, poly_search_time = multiple_parameter_search(
        X, y, poly_param_grid, poly_param_dist, kernel='poly', n_runs=2, cv=3
    )
    
    poly_model = SVC(kernel='poly', **poly_best_params, random_state=42)
    poly_model.fit(X_train, y_train)
    poly_accuracy = accuracy_score(y_test, poly_model.predict(X_test))
    
    # 存储调优结果
    results[dataset_name]['rbf_tuned'] = {
        'model': rbf_model,
        'accuracy': rbf_accuracy,
        'training_time': None,
        'search_time': rbf_search_time,
        'best_params': rbf_best_params,
        'avg_cv_score': rbf_avg_score
    }
    
    results[dataset_name]['poly_tuned'] = {
        'model': poly_model,
        'accuracy': poly_accuracy,
        'training_time': None,
        'search_time': poly_search_time,
        'best_params': poly_best_params,
        'avg_cv_score': poly_avg_score
    }
    
    # 绘制调优前后的决策边界对比
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    fig.suptitle(f'{dataset_name} - 核函数调优前后对比', fontsize=16)
    
    # RBF调优前
    original_rbf_model = results[dataset_name]['rbf']['model']
    original_rbf_accuracy = results[dataset_name]['rbf']['accuracy']
    plot_decision_boundary(X, y, original_rbf_model, axes[0, 0], 
                          f'RBF调优前 (准确率: {original_rbf_accuracy:.4f})')
    
    # RBF调优后
    plot_decision_boundary(X, y, rbf_model, axes[0, 1], 
                          f'RBF调优后 (准确率: {rbf_accuracy:.4f})')
    
    # 多项式调优前
    original_poly_model = results[dataset_name]['poly']['model']
    original_poly_accuracy = results[dataset_name]['poly']['accuracy']
    plot_decision_boundary(X, y, original_poly_model, axes[1, 0], 
                          f'多项式调优前 (准确率: {original_poly_accuracy:.4f})')
    
    # 多项式调优后
    plot_decision_boundary(X, y, poly_model, axes[1, 1], 
                          f'多项式调优后 (准确率: {poly_accuracy:.4f})')
    
    plt.tight_layout()
    experiment.save_plot(f"{dataset_name}_调优对比.png")
    plt.show()

total_tuning_time = time.time() - tuning_start_time

# 5. 性能对比与分析
print("\n5. 性能对比与分析")

# 输出总时间
print(f"\n总训练时间: {total_training_time:.2f}秒")
print(f"总调优时间: {total_tuning_time:.2f}秒")
print(f"总运行时间: {total_training_time + total_tuning_time:.2f}秒")

# 创建性能对比表格
print("\n性能对比表格:")
print("=" * 120)
print(f"{'数据集':<15} {'核函数':<12} {'准确率':<10} {'训练时间(秒)':<15} {'搜索时间(秒)':<15} {'平均CV得分':<12} {'最佳参数':<30}")
print("=" * 120)

for dataset_name in datasets.keys():
    dataset_results = results[dataset_name]
    
    for kernel in kernels:
        result = dataset_results[kernel]
        print(f"{dataset_name:<15} {kernel:<12} {result['accuracy']:<10.4f} {result['training_time']:<15.4f} {'N/A':<15} {'N/A':<12} {'':<30}")
    
    # 输出调优后的结果
    for tuned_kernel in ['rbf_tuned', 'poly_tuned']:
        tuned_result = dataset_results.get(tuned_kernel)
        if tuned_result:
            kernel_name = tuned_kernel.replace('_tuned', '(调优)')
            search_time = tuned_result.get('search_time', 'N/A')
            if search_time != 'N/A':
                search_time = f"{search_time:.2f}"
                
            print(f"{dataset_name:<15} {kernel_name:<12} {tuned_result['accuracy']:<10.4f} {'N/A':<15} {search_time:<15} {tuned_result['avg_cv_score']:<12.4f} {str(tuned_result['best_params']):<30}")

print("=" * 120)

# 简要分析
print("\n核函数选择与数据分布关系分析:")
print("1. 线性核: 在非线性可分数据上表现较差，适合线性可分数据")
print("2. 多项式核: 通过调整阶数可以适应一定程度的非线性，但可能过拟合")
print("3. RBF核: 最适合处理复杂的非线性可分数据，通过调整gamma参数控制决策边界复杂度")
print("4. 超参数调优: 多次运行网格搜索可以减少随机性影响，找到更稳定的参数组合")
print("5. 月亮数据集: 由于两个半月形有部分重叠，RBF核表现最好")
print("6. 环形数据集: 完全非线性可分，RBF核明显优于其他核函数")
print("7. 多次运行取平均: 通过多次运行网格搜索并取平均结果，可以减少数据划分随机性的影响")
print("8. 多项式核调优: 通过搜索degree参数，可以找到最适合数据复杂度的多项式阶数")

# 打印实验结束信息
experiment.print_footer()